package maxcache

import (
	"context"
	"sync"
	"sync/atomic"
	"time"
)

type MaxMemoryCache struct {
	Cache
	max  int64
	used int64
	// 维护每个key的到期时间
	lru     map[string]time.Time
	lruLock sync.Mutex
}

func NewMaxMemoryCache(max int64, cache Cache) *MaxMemoryCache {
	res := &MaxMemoryCache{
		Cache: cache,
		max:   max,
		lru:   make(map[string]time.Time),
	}

	res.Cache.OnEvicted(func(key string, val []byte) {
		atomic.AddInt64(&res.used, -int64(len(val)))
	})

	return res
}

// Get 获取缓存
func (m *MaxMemoryCache) Get(ctx context.Context, key string) ([]byte, error) {

	// 1. 获取缓存
	// 2. 更新lru
	// 3. 返回结果
	val, err := m.Cache.Get(ctx, key)

	if err != nil {
		return nil, err
	}

	m.lruLock.Lock()
	m.lru[key] = time.Now()
	m.lruLock.Unlock()

	return val, nil
}

func (m *MaxMemoryCache) Set(ctx context.Context, key string, val []byte,
	expiration time.Duration) error {
	// panic("implement me")

	// 1. 检查是否超过最大值
	// 2. 如果超过，删除最近最少使用的
	// 3. 如果没有超过，直接设置
	// 4. 如果设置成功，更新used
	// 5. 如果设置失败，返回错误
Loop:
	if m.used+int64(len(val)) > m.max {
		// 删除最近最少使用的-随机选择5个，删除最近最少使用的
		m.deleteLru()
		goto Loop
	}

	err := m.Cache.Set(ctx, key, val, expiration)

	if err != nil {
		return err
	}

	m.lruLock.Lock()
	m.lru[key] = time.Now()
	m.lruLock.Unlock()

	// 原子操作，更新used
	atomic.AddInt64(&m.used, int64(len(val)))

	return nil
}

// deleteLru 删除最近最少使用的，随机选择5个，删除最近最少使用的
func (m *MaxMemoryCache) deleteLru() {
	//  随机选择5个，删除最近最少使用的

	keys := make([]string, 0, 5)

	for i := 0; i < 5; i++ {
		// 随机选择一个
		for k := range m.lru {
			keys = append(keys, k)
			break
		}
	}

	// 选择最后一次使用时间最早的
	var minKey string
	var minTime time.Time

	for _, k := range keys {
		if t, ok := m.lru[k]; ok {
			if minKey == "" || t.Before(minTime) {
				minKey = k
				minTime = t
			}
		}
	}

	// 删除minKey
	if minKey != "" {
		err := m.Delete(context.Background(), minKey)

		if err == nil {
			m.lruLock.Lock()
			delete(m.lru, minKey)
			m.lruLock.Unlock()
		}
	}
}
